import akka.actor.Actor
import akka.actor.Actor._

// None of the EventHandler stuff will be on the Android client.
import akka.event.EventHandler

import java.net.URLClassLoader
import java.net.URL

import edu.colorado.cs._

object Client extends App {
  // Get a handle to the remote actor. In the Android client, the host
  // (second parameter) and port (third parameter) will be determined
  // based on user input.
  val actor = Actor.remote.actorFor("actor", "localhost", 1337)

  // This URL is for the URLClassLoader. It should be:
  // http://<host>:<port+1>/    (The ending slash is important!)
  //
  // The port for this URL is one more than the port defined by the user
  // because the URL is actually pointing to a different server that
  // serves class files.
  val urls = Array(new URL("http://localhost:1338/"))

  // This gets a new URLClassLoader that looks in the previously defined
  // URL for classes and with the default class loader as its parent.
  val cl = new URLClassLoader(urls, getClass().getClassLoader())

  // This initializes the Class object that represents the ProjectInfo
  // class we want an instance of. It's initialize to None because we
  // don't have it yet.
  var pi_class: Option[Class[ProjectInfo]] = None

  // Here we're actually loading the class via the URLClassLoader. If
  // there is an issue, a ClassNotFoundException is thrown. Scala handles
  // exceptions a little differently from Java.
  try {
    pi_class = Some(cl.loadClass("MyProjectInfo").asInstanceOf[Class[ProjectInfo]])
  } catch {
    case e: ClassNotFoundException => {
      // Remember: no event handler stuff on Android.
      EventHandler.error(this, "Class not found!")
      System.exit(1)
    }
  }

  // Here we're actually getting an instance of the ProjectInfo class
  // that we got from the server. Remember, this defines the name and
  // description of the project, and the function that the clients need
  // to exectute.
  val project_info: Option[ProjectInfo] = pi_class match {
    case Some(c: Class[ProjectInfo]) => Some(c.newInstance())
    case None => None
  }

  // This variable determines whether the client should continue
  // looking for data. This mechanism will be more complicated in the
  // Android client.
  var continue = true

  while (continue) {
    // The !! function sends a message to an actor and waits for a
    // response. This response needs to be pattern-matched in order to
    // determine what it was we got back.
    actor !! GetData() match {
      case Some(message: Data) => message match {
        // At this point we know we've got a Data message.
        // (see Messages.scala)
        case Data(value: Double) => {
          EventHandler.info(this, "Got " + value + " from server.")
          // In order to use the instance of ProjectInfo, we need to make
          // sure that we actually have an instance of ProjectInfo.
          project_info match {
            // Here's where the magic happens. If we've got some data and
            // we have an instance of ProjectInfo, then we send the server
            // a Result message that contains the input value and the
            // output as determined by the Function1 object stored in the
            // instance of ProjectInfo.
            //
            // Although p.func is actually a variable of type Function1,
            // it has a method called "apply" defined. That allows you to
            // "use" it as if it were a function.
            //
            // The ! function (we used !! before) sends a message and
            // does not wait for a response. It is asynchronous.
            case Some(p: ProjectInfo) => actor ! Result(value, p.func(value))
            case None => EventHandler.error(this, "No ProjectInfo")
          }
        }
        // I don't know whether this case is required. The underscore acts
        // as a wildcard in Scala. (In this case, anyway.) Think of this as
        // "default" in a switch statement. I don't know if getting here
        // is even possible.
        case _ => EventHandler.error(this, "Message didn't contain data.")
      }
      case Some(message: NoData) => {
        // In this case, we've received a NoData message. That indicates
        // that the server is out of data to send. (see Messages.scala)
        EventHandler.info(this, "Server out of data.")
        continue = false
      }
      // In this case, the server has failed to send a message to the
      // client.
      case None => EventHandler.error(this, "Bad things.")
    }
  }
}
